package org.molamil.acto.props {
	import org.molamil.acto.utils.ObjectUtils;
	import org.molamil.acto.utils.StringUtils;
	import org.molamil.acto.expressions.ExpressionResolver;
	import org.molamil.acto.core.session.ActoSession;
	import org.molamil.acto.core.events.ActoErrorEvent;			import flash.events.EventDispatcher;			import org.molamil.acto.core.parsers.ParserResolver;	
	import org.molamil.acto.types.DefaultTypeResolver;	
	import org.molamil.acto.core.loggers.Logger;	
	import org.molamil.acto.core.parsers.ParserError;
	
	/**
	 * XXX: Document.
	 * 
	 * @author Jorge Hernandez
	 */
	public class NativePropsParser extends EventDispatcher {
				// CONSTANTS
		
		private static const log:Logger = Logger.getLogger("NativePropsParser");
		
		public static const ANONYM_PROP_BASENAME:String = "prop";
		
		private static const ERROR_DUPLICATE_NAME:String = "ERROR: Duplicate prop name: '{name}'";
		private static const ERROR_PROPS_AND_ITEM:String = "ERROR: Both 'props' and 'item' nodes found at " +
			"the same level: '{level}'";
		private static const ERROR_MORE_PROPS:String = "ERROR: More than 1 'props' node found at the same " +
			"level: '{level}'";
		private static const ERROR_PROP_AND_PROPS:String = "ERROR: Both 'props' and 'prop' nodes found at the " +
			"same level: '{level}'";
		private static const ERROR_VALUE_ATTR_AND_CHILD:String = "ERROR: Value was found both as a 'value' attribute " +
			"and as a children at '{level}'";
		
		
		// PROPERTIES
		
		protected var session:ActoSession;
		protected var parsedProps:ParsedProps;
		protected var typeResolver:ParserResolver;
		
		
		// CONSTRUCTOR
		
		public function NativePropsParser(session:ActoSession = null) {
			this.session = session != null ? session : new ActoSession();
			// TODO: Make this configurable.
			typeResolver = new DefaultTypeResolver();
		}

		
		// PUBLIC METHODS
		
		public function parse(xml:XML):ParsedProps {
			
			log.info("parse()");
			
			parsedProps = new ParsedProps();
			parsedProps.source = xml;
			parsedProps.contexts = doParse(xml);
			parsedProps.props = extractStructValue(parsedProps.contexts);
			
			return parsedProps;
			
		}
		
		public function getPropDescriptor(node:XML, parent:PropDescriptor = null, useOnlyValueAttribute:Boolean = false):PropDescriptor {
			
			log.debug("getPropDescriptor()");
			
			var descriptor:PropDescriptor;
			
			if (node.hasSimpleContent() || useOnlyValueAttribute) {
				descriptor = getSimpleDescriptor(node, parent, useOnlyValueAttribute);
			} else {
				descriptor = getComplexDescriptor(node, parent);
			}
			
			// Check if type is forced.
			var type:String = node.@type;
			if (type != null && StringUtils.trim(node.@type) != "") {
				descriptor.type = ExpressionResolver.resolve(type, session.props);
				descriptor.value = ObjectUtils.convertObject(descriptor.value, descriptor.type);
			}
			
			return descriptor;
			
		}
		
		/**
		 * Creates and returns a <code>&lt;props&lt;</code> by extracting the <code>&lt;props&lt;</code> 
		 * and <code>&lt;prop&lt;</code> children found from a passed arbitrary xml structure, 
		 * discarding the other nodes.
		 * 
		 * @param xml Arbitrary XML structure to be scanned for <code>&lt;props&lt;</code> and 
		 * <code>&lt;prop&lt;</code> nodes.
		 * @return a new XML structure based on the passed XML containin only Acto Native nodes. 
		 */
		public function extractPropsXml(xml:XML):XML {
			
			// Process current xml.
			var inXml:XML = xml;
			var msg:String;
			
			// Check whether we have a a max of 1 <props> node and no <props> and <prop> nodes mixed.
			// Set inXml starting with the found <props> node if there's 1 <props> node in the input.
			var propsNodes:XMLList = xml["props"];
			var propNodes:XMLList = xml["prop"];
			if (propsNodes.length() == 1) {
				if (propNodes.length() == 0) {
					inXml = new XML(propsNodes[0].toString());
				} else {
					msg = ERROR_PROP_AND_PROPS.replace("{level}", xml.toXMLString());
					log.warning(msg);
					dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
													 msg, new ParserError(msg)));
				}
			} else if (propsNodes.length() > 1) {
				msg = ERROR_MORE_PROPS.replace("{level}", xml.toXMLString());
				log.warning(msg);
				dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
												 msg, new ParserError(msg)));
			}
			
			// Check that there are no prop or props nodes together with item elements.
			if ((propsNodes.length() > 0 || propNodes.length() > 0) && (xml["item"] != undefined)) {
				msg = ERROR_PROPS_AND_ITEM.replace("{level}", xml.toXMLString());
				log.warning(msg);
				dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
												 msg, new ParserError(msg)));
			}
			
			// Create the new xml.
			var outXml:XML = <props />;
			
			// Check and copy all subnodes.
			var i:Number;
			for (i = 0; i < inXml.children().length(); i++) {
				var prop:XML = inXml.children()[i];
				if (prop.localName() == "prop")
					outXml.appendChild(prop);
			}	
				
			return outXml;
			
		}
		
		public function getComplexDescriptor(node:XML, parent:PropDescriptor = null):PropDescriptor {
			
			log.debug("getComplexDescriptor()");
			
			var descriptor:PropDescriptor = createPropDescriptor();
			descriptor.source = node;
			descriptor.parent = parent;
			
			var msg:String;
			
			// Check when props or prop.
			if (node["props"] != undefined || node["prop"] != undefined) {
				
				// Check that there's no value attribute.
				if (node.@value != undefined) {
					msg = ERROR_VALUE_ATTR_AND_CHILD.replace("{level}", node.toXMLString());
					log.warning(msg);
					dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
													 msg, new ParserError(msg)));
				}
				
				descriptor.children = doParse(node, descriptor);
				
				descriptor.value = extractStructValue(descriptor.children);
			
			// Check when items.	
			} else if (node["item"] != undefined) {
				
				// Check that there's no value attribute.
				if (node.@value != undefined) {
					msg = ERROR_VALUE_ATTR_AND_CHILD.replace("{level}", node.toXMLString());
					log.warning(msg);
					dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
													 msg, new ParserError(msg)));
				}
				
				var itemNodes:XMLList = node["item"];
				var arrayContextResult:ArrayDescriptorResult = getArrayDescriptors(itemNodes, descriptor);
				descriptor.children = arrayContextResult.descriptors;
				descriptor.value = arrayContextResult.value;
				
			// Otherwise get the simple value.
			} else {
				
				descriptor.value = getSimpleDescriptor(node, descriptor).value;
				
			}
			
			return descriptor;
				 
		}
		
		public function getSimpleDescriptor(node:XML, parent:PropDescriptor = null, useOnlyValueAttribute:Boolean = false):PropDescriptor {
			
			log.debug("getSimpleDescriptor()");
			
			var descriptor:PropDescriptor = createPropDescriptor();
			descriptor.source = node;
			descriptor.parent = parent;
			
			// Check that there's no both value attribute and text content.
			if (node.@value != undefined && node.children().length() > 0) {
				var msg:String = ERROR_VALUE_ATTR_AND_CHILD.replace("{level}", node);
				log.warning(msg);
				dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
												 msg, new ParserError(msg)));
			}
			
			var value:*;
			
			// Take from null, value attribute or text content.
			if (node.@value == undefined && node.children().length() == 0) {
				log.debug("value is null");
				value = null;
			} else if (node.@value != undefined) {
				log.debug("value is in attribute");
				value = node.@value;
			} else if (!useOnlyValueAttribute) {
				log.debug("value is text node");
				value = node.toString();
			}
			
			// Set the source before parsing or resolving the expressions.
			descriptor.source = value;
			
			// Resolve expressions.
			if (value != null && session != null)
				value = ExpressionResolver.resolve(value, session.props);
			
			if (value == null) {
				if (session.useEmptyStringProps)
					value = "";	
			} else if (value is String) {
				value = typeResolver.parse(value);
			}
			
			descriptor.value = value;
			
			return descriptor;
				 
		}
		
		public function getArrayDescriptors(itemNodes:XMLList, parent:PropDescriptor = null):ArrayDescriptorResult {
			
			var result:ArrayDescriptorResult = new ArrayDescriptorResult();
			var descriptors:Object = new Object();
			var value:Array = new Array();
			var i:uint = 0;
			
			for each (var itemNode:XML in itemNodes) {
				var context:PropDescriptor = getPropDescriptor(itemNode, parent);
				descriptors[ANONYM_PROP_BASENAME + i] =  context;
				i++;
				value.push(context.value);
			}
			
			result.descriptors = descriptors;
			result.value = value;
			
			return result;
			
		}
		
		public function extractStructValue(contexts:Object):Object {
			
			var o:Object = new Object();
			
			for (var name:String in contexts) {
				var context:PropDescriptor = contexts[name];
				o[name] = context.value;
			}
			
			return o;
			
		}
		
		
		// PROTECTED METHODS
		
		protected function doParse(xml:XML, parent:PropDescriptor = null):Object {
			
			// Create the prop and propContext container as an object.
			var descriptors:Object; // of PropDescriptor
			
			// XXX: Consider remove the simple content check and create an ExternalNativePropsParser.
			
			if (xml.hasSimpleContent()) {
				
				log.debug("xml contains only simple content");
				
				// FIXME: See about simple content.
				//props = getSimpleValue(xml, parent);
				
			} else {
				
				descriptors = new Object();
			
				// Extract props.
				var formatXml:XML = extractPropsXml(xml);
				
				// Get nodes.
				var propNodes:XMLList = formatXml.prop;
				
				log.debug("props found: " + propNodes.length());
				
				// Iterate through all the prop nodes.
				for each (var node:XML in propNodes) {
					
					var name:String = node.@name;
					
					log.debug("processing prop: " + name);
					
					// TODO: Check for valid format in id/name. Create ObjectUtils.validateMemberName().
					
					// Check wether there is already a property .
					if (descriptors[name] != undefined) {
						var msg:String = ERROR_DUPLICATE_NAME.replace("{name}", name);
						log.warning(msg);
						dispatchEvent(new ActoErrorEvent(ActoErrorEvent.ACTO_ERROR, false, false, null, 
														 msg, new ParserError(msg)));
					}
					
					var descriptor:PropDescriptor = getPropDescriptor(node, parent);
					descriptor.name = name;
					descriptor.parent = parent;
					
					descriptors[name] = descriptor;
					
				}
				
			}
			
			return descriptors;
			
		}
		
		protected function createPropDescriptor():PropDescriptor {
			return new PropDescriptor();
		}
		
		
	}
	
}


// CLASS ARRAY CONTEXT RESULT

class ArrayDescriptorResult {
	
	
	// PROPERTIES
	
	public var descriptors:Object; // of PropDescriptor
	public var value:Array;
	

}
